package cc.rengu.redp.bizimpl.system.controller;

import cc.rengu.redp.bizimpl.bean.LoginPublicKey;
import cc.rengu.redp.bizimpl.constant.UpmpConstant;
import cc.rengu.redp.bizimpl.enums.ChannelTypeEnum;
import cc.rengu.redp.bizimpl.enums.UserPropertyKeyEnum;
import cc.rengu.redp.bizimpl.enums.UserTypeEnum;
import cc.rengu.redp.bizimpl.manager.UpmpUserManager;
import cc.rengu.redp.bizimpl.system.domain.UpmpActiveUser;
import cc.rengu.redp.bizimpl.upmp.dao.UpmpUserLoginLogMapper;
import cc.rengu.redp.bizimpl.upmp.domain.*;
import cc.rengu.redp.bizimpl.upmp.service.*;
import cc.rengu.redp.bizimpl.upmp.vo.UpmpUserInfoVO;
import cc.rengu.redp.bizimpl.usercenter.bean.UserInfo;
import cc.rengu.redp.bizimpl.utils.GZIPUtils;
import cc.rengu.redp.bizimpl.utils.RSAEncrypt;
import cc.rengu.redp.bizimpl.utils.RbdpClient;
import cc.rengu.redp.common.annotation.Limit;
import cc.rengu.redp.base.authentication.JWTTokenInfo;
import cc.rengu.redp.base.authentication.UpmpJWTUtil;
import cc.rengu.redp.common.domain.RedpResponse;
import cc.rengu.redp.common.exception.LoginFailedException;
import cc.rengu.redp.common.exception.RedpException;
import cc.rengu.redp.common.properties.RedpProperties;
import cc.rengu.redp.common.redis.RedisService;
import cc.rengu.redp.common.utils.AddressUtil;
import cc.rengu.redp.common.utils.DateUtil;
import cc.rengu.redp.common.utils.HashUtil;
import cc.rengu.redp.common.utils.IPUtil;
import cc.rengu.redp.system.domain.LoginCaptcha;
import cc.rengu.redp.system.domain.User;
import cc.rengu.redp.system.domain.UserConfig;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wf.captcha.SpecCaptcha;
import lombok.extern.slf4j.Slf4j;
import nonapi.io.github.classgraph.utils.VersionFinder;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.NotBlank;
import java.time.LocalDateTime;
import java.util.*;

@Slf4j
@Validated
@RestController
@RequestMapping("/admin")
public class UpmpLoginController {

    @Autowired
    private RedisService redisService;
    @Autowired
    private UpmpUserManager upmpUserManager;
    @Autowired
    private IUpmpUserInfoService upmpUserInfoService;
    @Autowired
    private IUpmpUserApexInfoService upmpUserApexInfoService ;
    @Autowired
    private IUpmpSysParamService upmpSysParamService;
    @Autowired
    private IUpmpUserLoginInfoService upmpUserLoginInfoService;
    @Autowired
    private IUpmpUserLoginLogService upmpUserLoginLogService;
    @Autowired
    private IUpmpOrganizationService upmpOrganizationService ;
    @Autowired
    private UpmpUserLoginLogMapper upmpUserLoginLogMapper;
    @Autowired
    private IUpmpMenuService upmpMenuService;
    @Autowired
    private RedpProperties properties;
    @Autowired
    private ObjectMapper mapper;

    @GetMapping("/loginCaptcha")
    public LoginCaptcha captcha() throws Exception {
        SpecCaptcha specCaptcha = new SpecCaptcha(116, 40, 4);
        String verCode = specCaptcha.text().toLowerCase();
//        String key = UUID.randomUUID().toString();
        String key = IdWorker.getIdStr(specCaptcha);
        redisService.set(UpmpConstant.LOGIN_CAPTCHA_PREFIX + key, verCode, 60000L);
        LoginCaptcha loginCaptcha = new LoginCaptcha();
        loginCaptcha.setVerKey(key);
        //已包含头部：data:image/png;base64
        loginCaptcha.setVerImage(specCaptcha.toBase64());
        loginCaptcha.setTestWarning(properties.isTestMode());
        return loginCaptcha;
    }

    @GetMapping("/loginPublicKey")
    public LoginPublicKey getPublicKeyParam() throws Exception {
        Map<String, String> keyPairMap = RSAEncrypt.genKeyPair();
        String key = IdWorker.getIdStr();
        redisService.hset(UpmpConstant.LOGIN_KEY_PAIR_PREFIX + key, keyPairMap);
        if(!ObjectUtils.isEmpty(keyPairMap.get(UpmpConstant.KEY_PAIR_MAP_PUBLIC_KEY))) {
//            redisService.set(UpmpConstant.LOGIN_PUBLIC_KEY_PREFIX + key, keyPairMap.get(UpmpConstant.KEY_PAIR_MAP_PUBLIC_KEY), 60000L);
            LoginPublicKey loginPublicKey = new LoginPublicKey();
            loginPublicKey.setVerKey(key);
            loginPublicKey.setVerPublicKey(keyPairMap.get(UpmpConstant.KEY_PAIR_MAP_PUBLIC_KEY));
            return loginPublicKey;
        }
        return null;
    }

    @GetMapping("/getInstInfoByUrl")
    public String getInstInfoByUrl(
            @NotBlank(message = "{required}") String url) throws Exception {
        String defaultInstId = upmpSysParamService.getById(UpmpConstant.IGAS_DEFAULT_INST_ID).getPrimaryValue();
        UpmpSysParam param = upmpSysParamService.findChildrenByPrimaryKeyAndParent(url,UpmpConstant.UPMP_URL_INSTID_PARAM,defaultInstId,null);
        if(ObjectUtils.isEmpty(param)){
            return defaultInstId;
        }
        return param.getPrimaryValue();
    }
    //PC端登录接口
    @PostMapping("/login")
    @Limit(key = "/admin/login", period = 60, count = 20, name = "登录接口", prefix = "limit")
    public RedpResponse login(
            @NotBlank(message = "{required}") String instId,
            @NotBlank(message = "{required}") String userType,
            @NotBlank(message = "{required}") String loginId,
            @NotBlank(message = "{required}") String password,
            @NotBlank(message = "{required}") String publicVerKey,
            @NotBlank(message = "{required}") String captchaKey,
            @NotBlank(message = "{required}") String captchaCode,
            HttpServletRequest request) throws Exception {

        if (!properties.isTestMode()) {
            String rightCode = redisService.get(UpmpConstant.LOGIN_CAPTCHA_PREFIX + captchaKey);
            if (rightCode == null) {
                throw new LoginFailedException("验证码已过期，请重新输入");
            }
            if (!rightCode.equals(captchaCode.trim().toLowerCase())) {
                throw new LoginFailedException("请输入正确的验证码");
            }
        }

//        loginId = StringUtils.lowerCase(loginId);
//        password = HashUtil.encryptGm(username, password);
        password = RSAEncrypt.decrypt(password,redisService.hgetValue(UpmpConstant.LOGIN_KEY_PAIR_PREFIX + publicVerKey,UpmpConstant.KEY_PAIR_MAP_PRIVATE_KEY));
        password =  HashUtil.encryptGm(loginId,password);
        final String errorMessage = "用户名或密码错误";

        String defaultInstId = upmpSysParamService.getById(UpmpConstant.IGAS_DEFAULT_INST_ID).getPrimaryValue();
        UpmpUserInfo user;
        if(defaultInstId.equals(instId)){//默认法人行根据userType+loginId查询，用户表增加userType+loginId唯一索引,对于登陆地址端口为默认法人行的 则可以登录默认法人行（运营中心）或法人行用户
            user = this.upmpUserInfoService.findByLoginId(userType,loginId);
        }else {
            if (UserTypeEnum.THIRD.getType().equals(userType)) {//第三方
                UpmpUserApexInfo userApexInfo = this.upmpUserApexInfoService.findByProperty(instId, userType, UserPropertyKeyEnum.APP_LOGIN_ID.getType(), loginId);
                user = this.upmpUserInfoService.findByUserId(instId, userApexInfo.getUserId());
            } else if (UserTypeEnum.BSPS.getType().equals(userType)) {//商户服务
                UpmpUserApexInfo userApexInfo = this.upmpUserApexInfoService.findByProperty(instId, userType, UserPropertyKeyEnum.OPEN_ID.getType(), loginId);
                user = this.upmpUserInfoService.findByUserId(instId, userApexInfo.getUserId());
            } else {//其他都认为是内管
                user = this.upmpUserInfoService.findUpmpUserInfo(instId, userType, loginId);
            }
        }

        if (user == null)
            throw new LoginFailedException(errorMessage);
        if (!StringUtils.equals(user.getLoginPassword(), password))
            throw new LoginFailedException(errorMessage);
        if (User.UserStatusEnum.BLOCK.getType().equals(user.getUserStatus()))
            throw new LoginFailedException("账号已被锁定,请联系管理员！");
        if (User.UserStatusEnum.CANCEL.getType().equals(user.getUserStatus()))
            throw new LoginFailedException("用户注销，无法登录");

        ChannelTypeEnum channelType = upmpMenuService.getChannel(userType,request);
        if(!ChannelTypeEnum.UPMP.getType().equals(channelType.getType())
                &&ChannelTypeEnum.BSPS_PC.getType().equals(channelType.getType())
                &&ChannelTypeEnum.MALL_PC.getType().equals(channelType.getType())){
            throw new LoginFailedException("不支持该终端访问！");
        }
        user.setChannelType(channelType.getType());

        // 更新用户登录时间
        this.upmpUserInfoService.updateLoginTime(user);
        // 保存登录记录
        UpmpUserLoginLog loginLog = new UpmpUserLoginLog();
        loginLog.setInstId(instId);
        loginLog.setUserId(user.getId());
        loginLog.setLoginId(loginId);
        loginLog.setUserType(userType);
        this.upmpUserLoginLogService.saveLoginLog(loginLog);

        /**
         * redis 存储数据：
         * 1、验证码：LOGIN_CAPTCHA_PREFIX+唯一标识 String
         * 2、公私钥对：LOGIN_KEY_PAIR_PREFIX+唯一标识  hashmap
         * 3、存储登录用户，score 为过期时间戳 : UpmpConstant.ACTIVE_USERS_ZSET_PREFIX+StringPool.DOT+user.getInstId()        zset
         * 4、redis 中存储这个加密 token，key = 前缀 + 加密 token + .ip
         * 5、redis中缓存的用户配置信息 RedpConstant.USER_CONFIG_CACHE_PREFIX + userId
         * **/

        String token = UpmpJWTUtil.encryptToken(UpmpJWTUtil.sign(user.getId(), password));
        LocalDateTime expireTime = LocalDateTime.now().plusSeconds(properties.getShiro().getJwtTimeOut());
        String expireTimeStr = DateUtil.formatFullTime(expireTime);
        JWTTokenInfo jwtTokenInfo = new JWTTokenInfo(token, expireTimeStr);

        UpmpUserLoginInfo loginInfo = new UpmpUserLoginInfo();
        loginInfo.setInstId(instId);
        loginInfo.setUserId(user.getId());
        loginInfo.setLoginType("WEB");
        loginInfo.setToken(token);
        upmpUserLoginInfoService.saveLoginInfo(loginInfo);
        log.info("登记用户[id:{},token:{}]登录信息成功",user.getId(),token);

        String userId = this.saveTokenToRedis(user, jwtTokenInfo, request);

        Map<String, Object> userInfo = this.generateUserInfo(jwtTokenInfo, user);
        //认证成功之后删除公私钥对
        redisService.del(UpmpConstant.LOGIN_KEY_PAIR_PREFIX + publicVerKey);
        return new RedpResponse().message("认证成功").data(userInfo);
    }

    @GetMapping("index/{userId}")
    public RedpResponse index(@NotBlank(message = "{required}") @PathVariable String userId) {
        UpmpUserInfo user = this.upmpUserInfoService.findById(userId);
        Map<String, Object> data = new HashMap<>();
        // 获取系统访问记录
        Long totalVisitCount = upmpUserLoginLogMapper.findTotalVisitCount(user.getInstId());
        data.put("totalVisitCount", totalVisitCount);
        Long todayVisitCount = upmpUserLoginLogMapper.findTodayVisitCount(user.getInstId());
        data.put("todayVisitCount", todayVisitCount);
        Long todayIp = upmpUserLoginLogMapper.findTodayIp(user.getInstId());
        data.put("todayIp", todayIp);
        // 获取近期系统访问记录
        List<Map<String, Object>> lastSevenVisitCount = upmpUserLoginLogMapper.findLastSevenDaysVisitCount(null);
        data.put("lastSevenVisitCount", lastSevenVisitCount);
//        User param = new User();
//        param.setUsername(username);
        List<Map<String, Object>> lastSevenUserVisitCount = upmpUserLoginLogMapper.findLastSevenDaysVisitCount(user);
        data.put("lastSevenUserVisitCount", lastSevenUserVisitCount);
        return new RedpResponse().data(data);
    }

    @RequiresPermissions("user:online")
    @GetMapping("online")
    public RedpResponse userOnline(@NotBlank(message = "{required}") String instId,String userId) throws Exception {
        String now = DateUtil.formatFullTime(LocalDateTime.now());
        Set<String> userOnlineStringSet = redisService.zrangeByScore(UpmpConstant.ACTIVE_USERS_ZSET_PREFIX+StringPool.DOT+instId, now, "+inf");
        List<UpmpActiveUser> activeUsers = new ArrayList<>();
        for (String userOnlineString : userOnlineStringSet) {
            UpmpActiveUser activeUser = mapper.readValue(userOnlineString, UpmpActiveUser.class);
            activeUser.setToken(null);
            if (StringUtils.isNotBlank(userId)) {
                if (StringUtils.equalsIgnoreCase(instId, activeUser.getInstId())&&StringUtils.equalsIgnoreCase(userId, activeUser.getUserId()))
                    activeUsers.add(activeUser);
            } else {
                activeUsers.add(activeUser);
            }
        }
        return new RedpResponse().data(activeUsers);
    }

    @DeleteMapping("kickout/{id}")
    @RequiresPermissions("user:kickout")
    public void kickout(@NotBlank(message = "{required}") @PathVariable String id) throws Exception {
        String now = DateUtil.formatFullTime(LocalDateTime.now());
        Set<String> userOnlineStringSet = redisService.zrangeByScore(UpmpConstant.ACTIVE_USERS_ZSET_PREFIX, now, "+inf");
        UpmpActiveUser kickoutUser = null;
        String kickoutUserString = "";
        for (String userOnlineString : userOnlineStringSet) {
            UpmpActiveUser activeUser = mapper.readValue(userOnlineString, UpmpActiveUser.class);
            if (StringUtils.equals(activeUser.getId(), id)) {
                kickoutUser = activeUser;
                kickoutUserString = userOnlineString;
                break;
            }
        }
        if (kickoutUser != null && StringUtils.isNotBlank(kickoutUserString)) {
            UpmpUserLoginInfo loginInfo = new UpmpUserLoginInfo();
            loginInfo.setInstId(kickoutUser.getInstId());
            loginInfo.setUserId(kickoutUser.getId());
            loginInfo.setLoginType("WEB");
            loginInfo.setToken(kickoutUser.getToken());
            upmpUserLoginInfoService.updateLogoutTime(loginInfo);

            // 删除 zset中的记录
            redisService.zrem(UpmpConstant.ACTIVE_USERS_ZSET_PREFIX, kickoutUserString);
            // 删除对应的 token缓存
            redisService.del(UpmpConstant.TOKEN_CACHE_PREFIX + kickoutUser.getToken() + StringPool.DOT + kickoutUser.getIp());
        }
    }

    @GetMapping("logout/{id}")
    public void logout(@NotBlank(message = "{required}") @PathVariable String id) throws Exception {
        this.kickout(id);
    }

    @PostMapping("regist")
    public void regist(
            @NotBlank(message = "{required}") String username,
            @NotBlank(message = "{required}") String password) throws Exception {
        this.upmpUserInfoService.regist(username, password);
    }

    private String saveTokenToRedis(UpmpUserInfo user, JWTTokenInfo token, HttpServletRequest request) throws Exception {
        String ip = IPUtil.getIpAddr(request);

        // 构建在线用户
        UpmpActiveUser activeUser = new UpmpActiveUser();
        activeUser.setId(user.getId());
        activeUser.setInstId(user.getInstId());
        activeUser.setUserId(user.getUserId());
        activeUser.setOrgCode(user.getOrgCode());
        activeUser.setUserName(user.getUserName());
        activeUser.setIp(ip);
        activeUser.setToken(token.getToken());
        activeUser.setLoginAddress(AddressUtil.getCityInfo(ip));

        // zset 存储登录用户，score 为过期时间戳
        this.redisService.zadd(UpmpConstant.ACTIVE_USERS_ZSET_PREFIX+StringPool.DOT+user.getInstId(), Double.valueOf(token.getExpireAt()), mapper.writeValueAsString(activeUser));
        // redis 中存储这个加密 token，key = 前缀 + 加密 token + .ip
        this.redisService.set(UpmpConstant.TOKEN_CACHE_PREFIX + token.getToken() + StringPool.DOT + ip, token.getToken(), properties.getShiro().getJwtTimeOut() * 1000);

        return activeUser.getId();
    }

    /**
     * 生成前端需要的用户信息，包括：
     * 1. token
     * 2. Vue Router
     * 3. 用户角色
     * 4. 用户权限
     * 5. 前端系统个性化配置信息
     *
     * @param token token
     * @param user  用户信息
     * @return UserInfo
     */
    private Map<String, Object> generateUserInfo(JWTTokenInfo token, UpmpUserInfo user) throws Exception {
        String userId = user.getId();
        Map<String, Object> userInfo = new HashMap<>();
        userInfo.put("token", token.getToken());
        userInfo.put("expireTime", token.getExpireAt());

        UpmpUserInfoVO userVo = upmpUserInfoService.findDetail(user.getId());

        String defaultInstId = upmpSysParamService.getById(UpmpConstant.IGAS_DEFAULT_INST_ID).getPrimaryValue();
        if(defaultInstId.equals(userVo.getInstId())){
            userVo.setAdmin(true);
        }else{
            userVo.setAdmin(false);
        }
        userVo.setChannelType(user.getChannelType());
//        List<UpmpRole> roles = this.upmpUserManager.getUserRoles(userId);
        List<UpmpRole> roles =userVo.getUserRoles();
        userInfo.put("roles", roles);

        Set<String> permissions = this.upmpUserManager.getUserPermissions(userId);
        userInfo.put("permissions", permissions);

        UserConfig userConfig = this.upmpUserManager.getUserConfig(user.getUserId());
        userInfo.put("config", userConfig);
        ;
//        this.upmpUserManager.loadUserRedisCache(user);
        //生成用户所属机构号及下级机构号之后更新redis缓存
        this.upmpUserManager.updateUserRedisCache(userVo);
        //生成联机需要的用户数据更新到redis
        UserInfo userObj = new UserInfo();
        BeanUtils.copyProperties(user,userObj);
        userObj.setUserOrgCodes(userVo.getUserOrgCodes());
        userObj.setUserOrgCodeArr(userVo.getUserOrgCodeArr());
        Map<String,String> userApexInfos = this.upmpUserApexInfoService.findPropertyMapByUserInfo(user.getInstId(),user.getUserId());
        userObj.setUserApexMap(userApexInfos);
        redisService.hset(token.getToken(),user.getInstId()+user.getUserId(), JSON.toJSONString(userObj));
        userVo.setLoginPassword("it's a secret");
        userInfo.put("user", userVo);
        return userInfo;
    }

}